/*
 * alloc memory method is kmem.
 *
 * Copyright (C) 2014 Ingenic Semiconductor Co.,Ltd
 * Author: Aeolus <weiqing.yan@ingenic.com>
 */

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <sys/mman.h>

#include <imp/imp_log.h>

#include "alloc_manager.h"
#include "linux_pmem.h"
#ifdef DMALLOC
#include <imp/dmalloc.h>
#endif
#define TAG "KMEM Method"

#define MEM_DEV "/dev/rmem"
#define CMD_LINE "/proc/cmdline"

static int kmem_fd = -1;
static uint32_t kmem_vaddr = 0;
static uint32_t kmem_paddr = 0;
static int kmem_length = 0;

struct rmem_flush_cache_info {
	unsigned int	addr;
	unsigned int	len;
	unsigned int	dir;
};

#define RMEM_CMD_FLUSH_CACHE   _IOWR('r', 0, struct rmem_flush_cache_info)

static unsigned long long memparse(const char *ptr, char **retptr)
{
	char *endptr;	/* local pointer to end of parsed string */

	unsigned long long ret = strtoull(ptr, &endptr, 0);

	switch (*endptr) {
	case 'G':
	case 'g':
		ret <<= 10;
	case 'M':
	case 'm':
		ret <<= 10;
	case 'K':
	case 'k':
		ret <<= 10;
		endptr++;
	default:
		break;
	}

	if (retptr)
		*retptr = endptr;

	return ret;
}

static int get_kmem_info(void)
{
	uint32_t addr = 0, size = 0;
	int ret;
	char buf[512] = "";
	char *p = NULL;

	FILE *fb = fopen(CMD_LINE, "r");
	if(fb == NULL) {
		IMP_LOG_ERR(TAG, "%s open file (%s) error\n", __func__, CMD_LINE);
		return -1;
	}

	ret = fread(buf, 1, sizeof(buf), fb);
	if(ret <= 0) {
		IMP_LOG_ERR(TAG, "%s fread (%s) error\n", __func__, CMD_LINE);
		return -1;
	}

	fclose(fb);

	p = strstr(buf, "rmem");
	if(p == NULL) {
		IMP_LOG_ERR(TAG, "%s fread (%s) error\n", __func__, CMD_LINE);
		return -1;
	}

	char *retptr;
	size = memparse(p + 5, &retptr);

	if (*retptr == '@')
		addr = memparse(retptr + 1, NULL);

	IMP_LOG_DBG(TAG, "CMD Line Rmem Size:%d, Addr:0x%08x\n", size, addr);

	kmem_paddr = addr;
	kmem_length = size;
	return 0;
}

int alloc_kmem_init(Alloc *alloc)
{
	int ret = -1;

	if(!(alloc)) {
		IMP_LOG_ERR(TAG, "%s function param is NULL\n", __func__);
		return -1;
	}

	ret = get_kmem_info();
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "get kmem info failed\n");
		return -1;
	}

	if(kmem_paddr <= 0 || kmem_length <= 0) {
		IMP_LOG_ERR(TAG, "%s mmap Addr %x and Size %d error\n", __func__, kmem_paddr, kmem_length);
		return -1;
	}

	kmem_fd	= open(MEM_DEV, O_RDWR);
	if(kmem_fd <= 0) {
		IMP_LOG_ERR(TAG, "open %s failed\n", MEM_DEV);
		return -1;
	}

	kmem_vaddr = (unsigned int)mmap(NULL, kmem_length, PROT_READ | PROT_WRITE, MAP_SHARED, kmem_fd, kmem_paddr);
	if(kmem_vaddr <= 0) {
		IMP_LOG_ERR(TAG, "mmap failed\n");
		return -1;
	}

	/*close(kmem_fd);*/

	alloc->mem_alloc.vaddr = kmem_vaddr;
	alloc->mem_alloc.paddr = kmem_paddr;
	alloc->mem_alloc.length = kmem_length;

	IMP_LOG_DBG(TAG, "alloc->mem_alloc.method = %s\n \
			alloc->mem_alloc.vaddr = 0x%08x\n \
			alloc->mem_alloc.paddr = 0x%08x\n \
			alloc->mem_alloc.length = %d\n",
			alloc->mem_alloc.method,
			alloc->mem_alloc.vaddr,
			alloc->mem_alloc.paddr,
			alloc->mem_alloc.length);

	return 0;
}

uint32_t alloc_kmem_get_paddr(uint32_t vaddr)
{
	if((vaddr < kmem_vaddr) || (vaddr > (kmem_vaddr + kmem_length))) {
		IMP_LOG_ERR(TAG, "vaddr input error %08x\n", vaddr);
		return 0;
	}

	return (kmem_paddr + (vaddr - kmem_vaddr));
}

uint32_t alloc_kmem_get_vaddr(uint32_t paddr)
{
	if((paddr < kmem_paddr) || (paddr > (kmem_paddr + kmem_length))) {
		IMP_LOG_ERR(TAG, "paddr input error\n");
		return 0;
	}

	return (kmem_vaddr + (paddr - kmem_paddr));
}

/**********************************************
 * dir value should be keep to be same as kernel
 * DMA_BIDIRECTIONAL = 0,
 * DMA_TO_DEVICE = 1,
 * DMA_FROM_DEVICE = 2,
 * DMA_NONE = 3,
**********************************************/
int alloc_kmem_flush_cache(void *vaddr, size_t size, uint32_t dir)
{
	if(kmem_fd < 0) {
		IMP_LOG_ERR(TAG, "%s not open\n", MEM_DEV);
		return -1;
	}

	struct rmem_flush_cache_info info = {(unsigned int)vaddr, size, dir};

	return ioctl(kmem_fd, RMEM_CMD_FLUSH_CACHE, &info);
}
